﻿Option Strict On

Imports System.IO
Imports System.Security.Cryptography
Imports System.Threading
Imports System.Text
Imports System.Text.RegularExpressions


Namespace ind.crypt
    ''' <summary>
    ''' Class to encrypt/decrypt data or files
    ''' </summary>
    ''' <remarks></remarks>
    Public Class Crypto

        '*******************************************************************
        'DECLARACIONES GLOBALES A NIVEL DE CLASE
        Dim crpEnc As ICryptoTransform
        Dim crpDec As ICryptoTransform
        Dim key() As Byte
        Dim IV() As Byte
        Dim rijndael As New RijndaelManaged
        '*******************************************************************

        ''' <summary>
        ''' Encrypt/Decrypt  action
        ''' </summary>
        ''' <remarks></remarks>
        Public Enum CryptoActionEnum
            'Define the enumeration for CryptoAction.
            ''' <summary>
            ''' Encrypt
            ''' </summary>
            ActionEncrypt = 1
            ''' <summary>
            ''' Decrypt
            ''' </summary>
            ''' <remarks></remarks>
            ActionDecrypt = 2
            ''' <summary>
            ''' app will be act depending on extension
            ''' </summary>
            ''' <remarks></remarks>
            ActionChoose = 3
        End Enum

        ''' <summary>
        ''' Constructor de la Clase
        ''' </summary>
        ''' <param name="hash">Clave hash para encriptar/desencriptar</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal hash As String)
            If hash Is Nothing OrElse hash = "" Then
                Throw New Exception(ind.lang.CoreLang.Crypto_PwdEmptyException)
            End If
            'redimensionamos los arrays de bytes con la clave hash
            RedimBytes(hash)
            'damos valores al encriptador/Desencriptador
            crpEnc = rijndael.CreateEncryptor(key, IV)
            crpDec = rijndael.CreateDecryptor(key, IV)

        End Sub

        ''' <summary>
        ''' Procedimiento RedimBytes redimensiona los arrays de bytes en funcion del hash creado.
        ''' </summary>
        ''' <param name="hash">hash. Cadena con la password encriptada</param>
        ''' <remarks></remarks>
        Private Sub RedimBytes(ByVal hash As String)
            Dim ms As New MemoryStream
            Dim bs As New StreamWriter(ms)
            ReDim key(15)
            ReDim IV(15)

            bs.Write(hash)
            bs.Flush()
            ms.Position = 0
            ms.Read(key, 0, key.Length)
            ms.Read(IV, 0, IV.Length)
            ms.Close()
            bs.Close()
        End Sub

        ''' <summary>
        ''' Función EncryptData. Función que encripta un texto que se le pasa a
        ''' la función por valor.
        ''' </summary>
        ''' <param name="inp">Cadena a encriptar</param>
        ''' <returns>Devuelve una cadena con el texto encriptado</returns>
        ''' <remarks></remarks>
        Public Function EncryptData2(ByVal inp As String) As String

            Dim sOut As String 'cadena a retornar con el texto encriptado
            Dim stream As New MemoryStream   'flujo de datos
            'Create a CryptoStream, pass it the stream, and encrypt it.
            Dim crypto As New CryptoStream(stream, crpEnc, CryptoStreamMode.Write)

            'creamos un StreamWriter para escribir en el stream
            Dim writer As New StreamWriter(crypto)
            'escribimos en el stream.
            writer.Write(inp)
            writer.Flush()
            crypto.FlushFinalBlock()

            Dim cipherTextBytes As Byte() = stream.ToArray()

            'cerramos las conexiones abiertas
            writer.Close()
            crypto.Close()
            stream.Close()

            'convertimos a base64 la cadena a devolver
            Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
            sOut = cipherText
            Return sOut

        End Function

        ''' <summary>
        ''' Funcion DecryptData. Función que desencripta un texto que se le pasa a
        ''' la función por valor.
        ''' </summary>
        ''' <param name="inp">Cadena a desencriptar</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function DecryptData2(ByVal inp As String) As String
            Dim sOut As String 'cadena a retornar con el texto encriptado
            Dim cipherTextBytes As Byte() = Convert.FromBase64String(inp)
            Dim stream As New MemoryStream(cipherTextBytes) 'lujo de datos
            'RedimBytes()
            'Create a CryptoStream, pass it the stream, and decrypt it.
            Dim crypto As New CryptoStream(stream, crpDec, CryptoStreamMode.Read)

            Dim plainTextBytes As Byte()
            ReDim plainTextBytes(cipherTextBytes.Length)

            Dim decryptedByteCount As Integer = crypto.Read(plainTextBytes, 0, plainTextBytes.Length)
            'Close all the connections.
            crypto.Close()
            stream.Close()

            sOut = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
            Return sOut
        End Function




#Region "Encrypt/Decrypt string"

        Public Function enCryptData(ByVal string2Encrypt As String) As String
            ' Create a symmetric Rijndael object for encryption.
            ' You are specifying the default values for the secret key and the initialization vector.
            ' These default values are generated when you create the first instance of the RijndaelManaged class.
            Dim Encryptor As ICryptoTransform = Me.rijndael.CreateEncryptor(Me.key, Me.IV)

            Dim stream As New MemoryStream() 'flujo de datos
            ' Initialize a new instance of the CryptoStream class with the destination file stream,
            ' the cryptographic transformation to use, and the mode of the stream.
            Dim CryptoWriteStream As New CryptoStream(stream, Encryptor, CryptoStreamMode.Write)
            ' Initialize a new stream writer for writing to the CryptoStream object.
            Dim MyStreamWriter As New StreamWriter(CryptoWriteStream)
            ' Write data to the CryptoStream object.
            MyStreamWriter.WriteLine(string2Encrypt)
            ' Close all streams and writers.
            MyStreamWriter.Close()
            CryptoWriteStream.Close()
            Dim cipherTextBytes As Byte() = stream.ToArray
            stream.Close()
            Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
            Return cipherText
        End Function

        Public Function DecryptData(ByVal string2Decrypt As String) As String
            ' Use a file stream to open the file that you had written the encrypted data to.
            Dim cipherTextBytes As Byte() = Convert.FromBase64String(string2Decrypt)
            Return DecryptData(cipherTextBytes)
        End Function

        Public Function DecryptData(cipherTextBytes As Byte()) As String

            Dim stream2Dec As New MemoryStream(cipherTextBytes) 'flujo de datos
            ' Create a symmetric Rijndael object for decryption.
            ' You are specifying the same secret key and initialization vector that you used to encrypt the data.
            Dim Decryptor As ICryptoTransform = Me.rijndael.CreateDecryptor(Me.key, Me.IV)

            ' Initialize a new instance of the CryptoStream class with the source file stream,
            ' the cryptographic transformation to use, and the mode of the stream.
            Dim CryptoReadStream As New CryptoStream(stream2Dec, Decryptor, CryptoStreamMode.Read)

            ' Initialize a new stream reader for reading from the CryptoStream object.
            Dim MyStreamReader As New StreamReader(CryptoReadStream)

            Dim cipherText As String = MyStreamReader.ReadToEnd

            ' Close all streams and writers.
            MyStreamReader.Close()
            CryptoReadStream.Close()
            stream2Dec.Close()

            Return cipherText.Trim

        End Function

        Public Function DecryptDataAsByte(cipherBytes As Byte()) As Byte()

            Dim bytesDecrypted() As Byte

            Using memStream As New MemoryStream()
                Dim Decryptor As ICryptoTransform = Me.rijndael.CreateDecryptor(Me.key, Me.IV)
                Using CryptoWriteStream As New CryptoStream(memStream, Decryptor, CryptoStreamMode.Write)

                    CryptoWriteStream.Write(cipherBytes, 0, cipherBytes.Length)
                    CryptoWriteStream.FlushFinalBlock()
                    bytesDecrypted = memStream.ToArray
                    memStream.Close()
                    CryptoWriteStream.Close()
                End Using
            End Using

            Return bytesDecrypted

        End Function

#End Region
#Region "Encrypt/Decrypt file"
        Public Sub enCryptFile(ByVal InputfileFullName As String, Optional OutputFullFilename As String = "")
            If OutputFullFilename = "" Then
                OutputFullFilename = InputfileFullName & ".indecrypt"
            End If

            Using fs2Encrypt As New FileStream(InputfileFullName, FileMode.Open, FileAccess.Read)
                ' Create a symmetric Rijndael object for encryption.
                ' You are specifying the default values for the secret key and the initialization vector.
                ' These default values are generated when you create the first instance of the RijndaelManaged class.
                Dim Encryptor As ICryptoTransform = Me.rijndael.CreateEncryptor(Me.key, Me.IV)

                Using outFileEncrypted As New FileStream(OutputFullFilename, FileMode.Create, FileAccess.Write)
                    ' Initialize a new instance of the CryptoStream class with the destination file stream,
                    ' the cryptographic transformation to use, and the mode of the stream.
                    Using CryptoWriteStream As New CryptoStream(outFileEncrypted, Encryptor, CryptoStreamMode.Write)
                        'Leer el texto del archivo en la matriz de bytes
                        Dim bytearrayinput(CInt(fs2Encrypt.Length - 1)) As Byte
                        fs2Encrypt.Read(bytearrayinput, 0, bytearrayinput.Length)
                        'Escribir el archivo cifrado con DES
                        CryptoWriteStream.Write(bytearrayinput, 0, bytearrayinput.Length)
                        CryptoWriteStream.Close()
                    End Using
                End Using
            End Using
        End Sub

        Public Function enCryptBytes(ByRef BytesToCypher As Byte()) As Byte()
            Dim bytesEncrypted() As Byte
            ' Create a symmetric Rijndael object for encryption.
            ' You are specifying the default values for the secret key and the initialization vector.
            ' These default values are generated when you create the first instance of the RijndaelManaged class.
            Dim Encryptor As ICryptoTransform = Me.rijndael.CreateEncryptor(Me.key, Me.IV)
            ' Initialize a new instance of the CryptoStream class with the destination file stream,
            ' the cryptographic transformation to use, and the mode of the stream.
            Using memStream As New MemoryStream()
                Using CryptoWriteStream As New CryptoStream(memStream, Encryptor, CryptoStreamMode.Write)
                    'Escribir el archivo cifrado con DES
                    CryptoWriteStream.Write(BytesToCypher, 0, BytesToCypher.Length)
                    CryptoWriteStream.FlushFinalBlock()
                    bytesEncrypted = memStream.ToArray
                    memStream.Close()
                    CryptoWriteStream.Close()
                End Using
            End Using

            Return bytesEncrypted

        End Function


        Public Sub deCryptFile(ByVal InputfileFullName As String, Optional OutputFullFilename As String = "")
            If OutputFullFilename = "" Then
                OutputFullFilename = InputfileFullName & ".indecrypt"
            End If
            Using fs2decrypt As New FileStream(InputfileFullName, FileMode.Open, FileAccess.Read)
                ' Create a symmetric Rijndael object for encryption.
                ' You are specifying the default values for the secret key and the initialization vector.
                ' These default values are generated when you create the first instance of the RijndaelManaged class.
                Dim Decryptor As ICryptoTransform = Me.rijndael.CreateDecryptor(Me.key, Me.IV)

                Using outFileDecrypted As New FileStream(OutputFullFilename, FileMode.Create, FileAccess.Write)
                    ' Initialize a new instance of the CryptoStream class with the destination file stream,
                    ' the cryptographic transformation to use, and the mode of the stream.
                    Using CryptoWriteStream As New CryptoStream(outFileDecrypted, Decryptor, CryptoStreamMode.Write)
                        'Leer el texto del archivo en la matriz de bytes
                        Dim bytearrayinput(CInt(fs2decrypt.Length - 1)) As Byte
                        fs2decrypt.Read(bytearrayinput, 0, bytearrayinput.Length)
                        'Escribir el archivo cifrado con DES
                        CryptoWriteStream.Write(bytearrayinput, 0, bytearrayinput.Length)
                        CryptoWriteStream.Close()
                    End Using
                End Using
            End Using
        End Sub

        Public Function deCryptFileAsStream(ByVal InputfileFullName As String) As Byte()

            Dim bytesDecrypted() As Byte

            Using fs2decrypt As New FileStream(InputfileFullName, FileMode.Open, FileAccess.Read)
                Dim bytearrayinput(CInt(fs2decrypt.Length - 1)) As Byte
                fs2decrypt.Read(bytearrayinput, 0, bytearrayinput.Length)
                Dim memStream As New MemoryStream()
                Dim Decryptor As ICryptoTransform = Me.rijndael.CreateDecryptor(Me.key, Me.IV)
                Using cStream As New CryptoStream(memStream, Decryptor, CryptoStreamMode.Write)

                    cStream.Write(bytearrayinput, 0, bytearrayinput.Length)
                    cStream.FlushFinalBlock()
                    bytesDecrypted = memStream.ToArray
                    memStream.Close()
                    cStream.Close()
                End Using
            End Using

            Return bytesDecrypted

        End Function

        Public Sub EncryptOrDecryptFile(ByVal strInputFile As String, _
                                         ByVal strOutputFile As String, _
                                         ByVal Direction As CryptoActionEnum)

            Dim fsInput As System.IO.FileStream = Nothing
            Dim fsOutput As System.IO.FileStream = Nothing

            Try
                Dim strOutputFileTemp As Boolean = False
                If strInputFile = strOutputFile Then
                    strOutputFile = strOutputFile & "." & ind.cte.AppConst.fileCryptExtension & "temp"
                    strOutputFileTemp = True
                End If


                'Setup file streams to handle input and output.
                fsInput = New System.IO.FileStream(strInputFile, FileMode.Open, FileAccess.Read)
                fsOutput = New System.IO.FileStream(strOutputFile, FileMode.OpenOrCreate, FileAccess.Write)
                fsOutput.SetLength(0) 'make sure fsOutput is empty

                'Declare variables for encrypt/decrypt process.
                Dim bytBuffer(4096) As Byte 'holds a block of bytes for processing
                Dim lngBytesProcessed As Long = 0 'running count of bytes processed
                Dim lngFileLength As Long = fsInput.Length 'the input file's length
                Dim intBytesInCurrentBlock As Integer 'current bytes being processed
                Dim csCryptoStream As CryptoStream = Nothing
                'Declare your CryptoServiceProvider.
                Dim cspRijndael As New System.Security.Cryptography.RijndaelManaged

                'Determine if ecryption or decryption and setup CryptoStream.
                Select Case Direction
                    Case CryptoActionEnum.ActionEncrypt
                        csCryptoStream = New CryptoStream(fsOutput, _
                        cspRijndael.CreateEncryptor(key, IV), _
                        CryptoStreamMode.Write)

                    Case CryptoActionEnum.ActionDecrypt
                        csCryptoStream = New CryptoStream(fsOutput, _
                        cspRijndael.CreateDecryptor(key, IV), _
                        CryptoStreamMode.Write)
                End Select

                'Use While to loop until all of the file is processed.
                While lngBytesProcessed < lngFileLength
                    'Read file with the input filestream.
                    intBytesInCurrentBlock = fsInput.Read(bytBuffer, 0, 4096)
                    'Write output file with the cryptostream.
                    csCryptoStream.Write(bytBuffer, 0, intBytesInCurrentBlock)
                    'Update lngBytesProcessed
                    lngBytesProcessed = lngBytesProcessed + CLng(intBytesInCurrentBlock)

                End While

                'Close FileStreams and CryptoStream.
                csCryptoStream.Close()
                fsInput.Close()
                fsOutput.Close()

                'If encrypting then delete the original unencrypted file.
                If Direction = CryptoActionEnum.ActionEncrypt Then
                    Try
                        Dim fileOriginal As New FileInfo(strInputFile)
                        fileOriginal.Delete()
                    Catch exIO As IO.IOException
                        Throw New ind.exceptions.FileInUseException(ind.lang.CoreLang.Crypto_OutputFileInUseByAnotherProcessException, exIO)
                    Catch ex As Exception
                        'todo: after encryption file can not delete? Why? attributes?
                        Debug.WriteLine(ex)
                        Throw ex
                    End Try

                End If

                'If decrypting then delete the encrypted file.
                If Direction = CryptoActionEnum.ActionDecrypt Then
                    Dim fileEncrypted As New FileInfo(strInputFile)
                    Dim originalNameForOutputFileTemp As String = fileEncrypted.Name
                    fileEncrypted.Delete()
                    If strOutputFileTemp = True Then
                        My.Computer.FileSystem.RenameFile(strOutputFile, originalNameForOutputFileTemp)
                    End If
                End If


                'Catch file not found error.
            Catch When Err.Number = 53 'if file not found

                Throw New FileNotFoundException(ind.lang.CoreLang.Crypto_OutputFileNotFoundDuringEncriptionException)


            Catch exFile As ind.exceptions.FileInUseException
                If Direction = CryptoActionEnum.ActionEncrypt Then
                    Throw exFile
                End If
                'Catch all other errors. And delete partial files.
            Catch exCrypt1 As System.Security.Cryptography.CryptographicException
                Debug.WriteLine(exCrypt1)
                fsInput.Close()
                fsOutput.Close()
                If Direction = CryptoActionEnum.ActionDecrypt Then
                    Dim fileDelete As New FileInfo(strOutputFile)
                    fileDelete.Delete()
                    ind.encrypter.AppEventsClass.raisePasswordIncorrectEvent()
                    Throw New ind.exceptions.FileNotDecryptedException(ind.lang.CoreLang.Crypto_EncryptOrDecryptFile_FileNotEncryptedException & vbCr & strInputFile)
                Else
                    Dim fileDelete As New FileInfo(strInputFile)
                    fileDelete.Delete()
                    Throw New Exception(ind.lang.CoreLang.Crypto_EncryptOrDecryptFile_FileCanNotEncryptedException)
                End If


            Catch ex As Exception
                fsInput.Close()
                fsOutput.Close()
                Debug.WriteLine(ex)
                If Direction = CryptoActionEnum.ActionDecrypt Then
                    Dim fileDelete As New FileInfo(strOutputFile)
                    fileDelete.Delete()
                    ind.encrypter.AppEventsClass.raisePasswordIncorrectEvent()
                    Throw New Exception(ind.lang.CoreLang.Crypto_PwdDecryptionException)
                Else
                    Dim fileDelete As New FileInfo(strOutputFile)
                    fileDelete.Delete()
                    Throw New Exception(ind.lang.CoreLang.Crypto_EncryptOrDecryptFile_FileCanNotEncryptedException)
                End If

            End Try
        End Sub



#End Region

        Public Enum PasswordScore
            Blank = 0
            VeryWeak = 1
            Weak = 2
            Medium = 3
            GOOD = 4
            Strong = 5
            VeryStrong = 6
        End Enum
        ''' <summary>
        ''' check the strength of password
        ''' </summary>
        ''' <param name="password"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function CheckStrength(ByVal password As String) As PasswordScore
            Dim score As Integer = 1

            If password.Length < 1 Then
                Return PasswordScore.Blank
            End If
            If password.Length < 4 Then
                Return PasswordScore.VeryWeak
            End If

            If password.Length >= 5 Then
                score = score + 1
            End If
            If password.Length >= 10 Then
                score = score + 1
            End If

            If Regex.IsMatch(password, "/\d+/") Then
                score = score + 1
            End If

            If Regex.IsMatch(password, "/[a-z]/") Then

                score = score + 1
            End If

            If Regex.IsMatch(password, "/[0-9]/") Then

                score = score + 1
            End If

            If Regex.IsMatch(password, "/[A-Z]/") Then
                score = score + 1
            End If
            If Regex.IsMatch(password, "/[.,!,@,#,$,%,^,&]/") Then
                score = score + 1
            End If
            If Regex.IsMatch(password, "/[*,?,_,~,-,£,(,)]/") Then
                score = score + 1
            End If


            Return CType(score, PasswordScore)
        End Function

        ''' <summary>Determines if a password is sufficiently complex.</summary> 
        ''' <param name="pwd">Password to validate</param> 
        ''' <param name="minLength">Minimum number of password characters.</param> 
        ''' <param name="numUpper">Minimum number of uppercase characters.</param> 
        ''' <param name="numLower">Minimum number of lowercase characters.</param> 
        ''' <param name="numNumbers">Minimum number of numeric characters.</param> 
        ''' <param name="numSpecial">Minimum number of special characters.</param> 
        ''' <returns>True if the password is sufficiently complex.</returns> 
        Public Shared Function ValidatePasswordQuality(ByVal pwd As String,
            Optional ByVal minLength As Integer = 8,
            Optional ByVal numUpper As Integer = 2,
            Optional ByVal numLower As Integer = 2,
            Optional ByVal numNumbers As Integer = 2,
            Optional ByVal numSpecial As Integer = 2) As Boolean

            ' Replace [A-Z] with \p{Lu}, to allow for Unicode uppercase letters. 
            Dim upper As New System.Text.RegularExpressions.Regex("[A-Z]")
            Dim lower As New System.Text.RegularExpressions.Regex("[a-z]")
            Dim number As New System.Text.RegularExpressions.Regex("[0-9]")
            ' Special is "none of the above". 
            Dim special As New System.Text.RegularExpressions.Regex("[^a-zA-Z0-9]")

            ' Check the length. 
            If Len(pwd) < minLength Then Return False
            ' Check for minimum number of occurrences. 
            If upper.Matches(pwd).Count < numUpper Then Return False
            If lower.Matches(pwd).Count < numLower Then Return False
            If number.Matches(pwd).Count < numNumbers Then Return False
            If special.Matches(pwd).Count < numSpecial Then Return False

            ' Passed all checks. 
            Return True
        End Function

    End Class
End Namespace